/*          ._
 *            '.			     _______ _______ ___ ___ _______ 
 *     --._     \    .-.		|   |   |_     _|   |   |    ___|
 *         '. .--;  /  |		|       |_|   |_|   |   |    ___|
 *         ,;/ ^  |`\  /.-"".   |___|___|_______|\_____/|_______|	
 *        ;' \  _/   |'    .'          code.google.com/p/as-hive
 *       /    `.;I> /_.--.` )    __
 *      |    .'`  .'( _.Y/`;    /  \__
 *       \_.'---'`   `\  `-/`-. \__/  \__
 *         /_-.`-.._ _/`\  ;-./_/  \__/  \
 *        |   -./  ;.__.'`\  /  \__/  \__/
 *         `--'   (._ _.'`|  \__/  \__/
 *                /     ./ __/  \__/  \
 *           jgs ; `--';'_/  \__/  \__/
 *               `;-,-'/  \__/  \
 *               ,'`   \__/  \__/
 *
 * Copyright 2007 (c) Jovan Alleyne, Peter Nitsch, Brandon Flowers.
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

package org.hive.managers
{
	import flash.events.EventDispatcher;
	import flash.events.MouseEvent;
	import flash.events.TextEvent;
	import org.hive.Hive;
	import flash.events.IEventDispatcher;
	import flash.events.KeyboardEvent;

	import org.hive.state.*;
	import org.hive.animation.*
	import org.hive.data.*;
	import org.hive.sequence.*;
	import org.hive.managers.*;
	import org.hive.events.*;
	import org.hive.media.*;
		
	import fl.motion.Animator;
	
	import flash.display.DisplayObjectContainer;
	import flash.events.Event;
	import flash.utils.Dictionary;
	import flash.utils.getQualifiedClassName;

    /**
    *
    */
	public class StateManager extends Manager
	{
		//_________________________________________________________________________________________________ PUBLIC PROPERTIES


        /**
        *
        */
		public static var sequencer 		: Sequencer;

        /**
        *
        */
		public static var animationLib 		: AnimationLibrary;

        /**
        *
        */
		public static var contentList 		: ContentList;

        /**
        *
        */
		public static var currentState 		: State;

        /**
        *
        */
        public static var nextState         : State;

        /**
        *
        */
		public static var documentClass		: DisplayObjectContainer;
		
		//_________________________________________________________________________________________________ PRIVATE PROPERTIES

        /**
        *
        */
        private static var __stateCloseEvent        : Event;

        /**
        *
        */
        private static var __activeSequenceDelays   : Dictionary = new Dictionary( false );

        /**
        *
        */
		private static var __cancelList             : Dictionary=new Dictionary();

		

		
		
		
		
		
		//_________________________________________________________________________________________________ PUBLIC METHODS

		
        /**
        *
        * @param	target
        */
		public function StateManager( target:IEventDispatcher=null )
		{
			super(target);
		}
		
        /**
        *
        * @param	cl
        * @param	initState
        * @param	stage
        * @return
        */
		public static function init( cl:ContentList, initState:State, stage:DisplayObjectContainer ):void
		{
			sequencer		= new Sequencer( );
			animationLib 	= new AnimationLibrary( );
			
			documentClass = stage;
			currentState = initState;
			
			contentList = cl;
			contentList.addEventListener( ContentListEvent.ON_READY, onContentListReady, false, 0, true );
		}
		

        /**
        *
        * @param	newState		Instance of application state to switch to.
        * @param	e				Event instance that caused state switch.
        * @return
        */
		public static function switchState( newState:State, e:Event=null ):void
		{
			trace('CoreManager :: switchState');

            nextState = newState;
            __stateCloseEvent = e;
            //
            var closeSequence:CloseSequence = currentState.close( );

            if( closeSequence ){
                playSequence( closeSequence );
            }
            else executeStateSwitch( );
		}

        /**
        *
        * @param	name
        * @return
        */
		public static function cancelSequence( name:String ):Boolean
		{
            trace('StateManager :: cancelSequence : '+name);

			if( name is String ){
				__cancelList[ name ] = true;
                executeCancelSequence( name );
				return true;
			}
			return false;
		}

		protected static var actionLib				:Dictionary = new Dictionary(false);
		protected static var tweenableActionLib		:Dictionary = new Dictionary(false);
				
		/**
        *
        * @param	definition		Registers the class definition to the action library.
        * @param	callback		Registers the callback function with the class definition: callback( action:*, sequence:Sequence, positionInSequence:int ).
		* @param	tweenable		Use Tweener if true.
        */
		
		public static function registerAction( definition:Class, callback:Function, tweenable:Boolean=false )
		{
			if ( tweenable )
				tweenableActionLib[ definition ] = callback;
			else
				actionLib[ definition ] = callback;
		}
		
		/**
        *
        * @param	definition		Unregisters the class definition from the action library.
		* @param	tweenable		Unregisters tweenable action if true.
        */
		
		public static function unregisterAction( definition:Class, tweenable:Boolean=false )
		{
			if ( tweenable )
				delete tweenableActionLib[ definition ];
			else
				delete actionLib[ definition ];
		}
		
        /**
        *
        * @param	sequence		Instance of sequence to play.
        * @return
        */
		public static function playSequence( sequence:Sequence ):void
		{
			trace('StateManager :: playSequence : ' + sequence.name);
			
			if( !sequence.active ){
                sequencer.currentSequence = sequencer.add( sequence );
				sequence.activate();
			}

            if( !sequence.hasEventListener( SequenceEvent.ON_COMPLETE ) && sequence is ActionSequence )
            sequence.addEventListener( SequenceEvent.ON_COMPLETE, onManagerChanged, false, 0, true );
			
			for ( var i : Number = 0; i < sequence.length; i++ ) {
				
				var action			:* = sequence.index(i);
				var scope			:*;
				var actionPlayed	:Boolean = false;
				
				for ( var definition:* in actionLib ) {
					if ( action is definition ) {
						actionLib[ definition ]( action, sequence, i );
						actionPlayed = true;
					}
				}
				
				if( action is SequenceDelay )
                {
                    if( (i + 1) < sequence.length )
                    sequence.removeEventListener( SequenceEvent.ON_COMPLETE, onManagerChanged );

                    if( !__activeSequenceDelays[ sequence.name ] )
                    __activeSequenceDelays[ sequence.name ] =  new Dictionary( );

                    __activeSequenceDelays[ sequence.name ][ action ] = i;

					action.start( onExecuteResumed, sequence.slice( ++i ) );
					return;
				} 
				else if( action is SequenceFunction )
                {
                    var sequenceFunction:SequenceFunction = action;
                    sequenceFunction.method.apply( sequenceFunction.scope, sequenceFunction.argumentsList );
                }
				else if ( action as Object && action.hasOwnProperty("scope") )
				{
					for ( var conditionalClass:* in tweenableActionLib ) {
						if ( action.scope as conditionalClass != null ) {
							tweenableActionLib[ conditionalClass ]( action, sequence, i );
						}
					}
				}
				else
				{
					if ( !actionPlayed )
					{
						trace("StateManager :: playSequence : Action definition not registered, " + action);
					}
				}
			}

            if( sequence is CloseSequence ) executeStateSwitch( );
            else sequence.complete = true;
		}
		
		//_________________________________________________________________________________________________ PRIVATE METHODS

        /**
        *
        * @param	state
        * @return
        */
		private static function configState( state:State ):State
		{
			state.contentList	 = contentList;
			state.sequencer 	 = sequencer;

			return state;
		}

        /**
        *
        * @param	manager		Register manager instance to capture events through the StateManager.
        */
		public static function registerManager( manager:* ):void
		{
			manager.addEventListener( ManagerEvent.ON_CHANGED, onManagerChanged, false, 0, true );
		}
		
        
        /**
        *
        * @param	e
        * @return
        */
		private static function onContentListReady( e:ContentListEvent ):void
		{
			currentState = configState( currentState );
			
			currentState.previousState 	 = null;
			currentState.trigger 		 = null;

			var sequence:ActionSequence = currentState.create( );
			playSequence( sequence );
		}
		
        /**
        *
        * @param	e
        * @return
        */
		private static function onManagerChanged( e:* ):void
		{
            trace('StateManager :: onManagerChanged :: '+e.type);

			if( e as SequenceEvent != null )
			e = e as SequenceEvent;
			else e = e as ManagerEvent;
			
			if( !e.switchState && e.nextSequence ){
				playSequence( e.nextSequence );
			} else {
				if( e.content != null ){
					switchState( e.content, e );
				}
			}
		}

        /**
        *
        * @return
        */
		private static function executeStateSwitch( ):ActionSequence
        {
            var ls:State = currentState;
            currentState = configState( nextState );

            nextState = null;
			
			currentState.previousState 	 = ls;
			currentState.trigger 		 = __stateCloseEvent;
            
			dispatchEvent( new StateManagerEvent(StateManagerEvent.ON_STATE_SWITCHED, currentState.previousState, currentState) );
			
			var sequence:ActionSequence = currentState.create( );
			playSequence( sequence );

            return sequence;
        }

        /**
        *
        * @param	name
        * @return
        */
        private static function executeCancelSequence( name:String ):Boolean
        {
           trace('StateManager :: executeCancelSequence : '+name);

            var sequence:Sequence;

            if( __cancelList[ name ] &&
                ( sequence = sequencer.getSequenceByName( name )) != null ){

                sequence.kill( );

                for( var delay in __activeSequenceDelays[ name ] ){
                    delay.stop( );
                    delete __activeSequenceDelays[ name ][ delay ];
                }

                delete __cancelList[ name ];

                var event:SequenceEvent = new SequenceEvent( SequenceEvent.ON_CANCEL );
                sequence.dispatchEvent( event );

                return true;
            }
            else return false;
        }
		
        /**
        *
        * @param	e
        * @return
        */
		private static function onExecuteResumed( e:SequenceDelayEvent ):void
		{
            delete __activeSequenceDelays[ e.sequence.name ];

            if( !e.sequence.length ) return;

			if( __cancelList[ e.sequence.name ] ) {
                executeCancelSequence( e.sequence.name );
				return;
			}
			else playSequence( e.sequence );
		}

       
		/**
        *
        */
		public static var blocked		: Boolean = false;
		
		/**
        *
        * 
        */
		public static function blockInteraction():void
		{
			blocked = true;
			documentClass.addEventListener( MouseEvent.CLICK, handleBlockInteraction, true, 0, true );
			documentClass.addEventListener( MouseEvent.MOUSE_DOWN, handleBlockInteraction, true, 0, true );
			documentClass.addEventListener( TextEvent.LINK, handleBlockInteraction, true, 0, true );
		}
		
		/**
        *
        * 
        */
		public static function unblockInteraction():void
		{
			blocked = false;
			documentClass.removeEventListener( MouseEvent.CLICK, handleBlockInteraction, true );
			documentClass.removeEventListener( MouseEvent.MOUSE_DOWN, handleBlockInteraction, true );
			documentClass.removeEventListener( TextEvent.LINK, handleBlockInteraction, true );
		}
		
		/**
        *
        * @param	e
        */
		private static function handleBlockInteraction( e:Event ):void
		{
			e.stopImmediatePropagation();
		}
		
		
		
		
		
		//_________________________________________________________________________________________________ STATIC EVENT DISPATCHER METHODS
		protected static var disp:EventDispatcher;
		
		public static function addEventListener(p_type:String, p_listener:Function, p_useCapture:Boolean=false, p_priority:int=0, p_useWeakReference:Boolean=false):void {
			if (disp == null) { disp = new EventDispatcher(); }
			disp.addEventListener(p_type, p_listener, p_useCapture, p_priority, p_useWeakReference);
		}
		
		public static function removeEventListener(p_type:String, p_listener:Function, p_useCapture:Boolean=false):void {
			if (disp == null) { return; }
			disp.removeEventListener(p_type, p_listener, p_useCapture);
		}
		
		public static function dispatchEvent(p_event:Event):void {
			if (disp == null) { return; }
			disp.dispatchEvent(p_event);
		}
		
	}
}